// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

(function(mod) {
  if (typeof exports == 'object' && typeof module == 'object')
    // CommonJS
    mod(require('../../lib/codemirror'));
  else if (typeof define == 'function' && define.amd)
    // AMD
    define(['../../lib/codemirror'], mod);
  // Plain browser env
  else mod(CodeMirror);
})(function(CodeMirror) {
  'use strict';

  CodeMirror.defineMode('q', function(config) {
    var indentUnit = config.indentUnit,
      curPunc,
      keywords = buildRE([
        'abs',
        'acos',
        'aj',
        'aj0',
        'all',
        'and',
        'any',
        'asc',
        'asin',
        'asof',
        'atan',
        'attr',
        'avg',
        'avgs',
        'bin',
        'by',
        'ceiling',
        'cols',
        'cor',
        'cos',
        'count',
        'cov',
        'cross',
        'csv',
        'cut',
        'delete',
        'deltas',
        'desc',
        'dev',
        'differ',
        'distinct',
        'div',
        'do',
        'each',
        'ej',
        'enlist',
        'eval',
        'except',
        'exec',
        'exit',
        'exp',
        'fby',
        'fills',
        'first',
        'fkeys',
        'flip',
        'floor',
        'from',
        'get',
        'getenv',
        'group',
        'gtime',
        'hclose',
        'hcount',
        'hdel',
        'hopen',
        'hsym',
        'iasc',
        'idesc',
        'if',
        'ij',
        'in',
        'insert',
        'inter',
        'inv',
        'key',
        'keys',
        'last',
        'like',
        'list',
        'lj',
        'load',
        'log',
        'lower',
        'lsq',
        'ltime',
        'ltrim',
        'mavg',
        'max',
        'maxs',
        'mcount',
        'md5',
        'mdev',
        'med',
        'meta',
        'min',
        'mins',
        'mmax',
        'mmin',
        'mmu',
        'mod',
        'msum',
        'neg',
        'next',
        'not',
        'null',
        'or',
        'over',
        'parse',
        'peach',
        'pj',
        'plist',
        'prd',
        'prds',
        'prev',
        'prior',
        'rand',
        'rank',
        'ratios',
        'raze',
        'read0',
        'read1',
        'reciprocal',
        'reverse',
        'rload',
        'rotate',
        'rsave',
        'rtrim',
        'save',
        'scan',
        'select',
        'set',
        'setenv',
        'show',
        'signum',
        'sin',
        'sqrt',
        'ss',
        'ssr',
        'string',
        'sublist',
        'sum',
        'sums',
        'sv',
        'system',
        'tables',
        'tan',
        'til',
        'trim',
        'txf',
        'type',
        'uj',
        'ungroup',
        'union',
        'update',
        'upper',
        'upsert',
        'value',
        'var',
        'view',
        'views',
        'vs',
        'wavg',
        'where',
        'where',
        'while',
        'within',
        'wj',
        'wj1',
        'wsum',
        'xasc',
        'xbar',
        'xcol',
        'xcols',
        'xdesc',
        'xexp',
        'xgroup',
        'xkey',
        'xlog',
        'xprev',
        'xrank',
      ]),
      E = /[|/&^!+:\\\-*%$=~#;@><,?_\'\"\[\(\]\)\s{}]/;
    function buildRE(w) {
      return new RegExp('^(' + w.join('|') + ')$');
    }
    function tokenBase(stream, state) {
      var sol = stream.sol(),
        c = stream.next();
      curPunc = null;
      if (sol)
        if (c == '/') return (state.tokenize = tokenLineComment)(stream, state);
        else if (c == '\\') {
          if (stream.eol() || /\s/.test(stream.peek()))
            return (
              stream.skipToEnd(),
              /^\\\s*$/.test(stream.current())
                ? (state.tokenize = tokenCommentToEOF)(stream)
                : (state.tokenize = tokenBase),
              'comment'
            );
          else return (state.tokenize = tokenBase), 'builtin';
        }
      if (/\s/.test(c))
        return stream.peek() == '/'
          ? (stream.skipToEnd(), 'comment')
          : 'whitespace';
      if (c == '"') return (state.tokenize = tokenString)(stream, state);
      if (c == '`') return stream.eatWhile(/[A-Za-z\d_:\/.]/), 'symbol';
      if (('.' == c && /\d/.test(stream.peek())) || /\d/.test(c)) {
        var t = null;
        stream.backUp(1);
        if (
          stream.match(
            /^\d{4}\.\d{2}(m|\.\d{2}([DT](\d{2}(:\d{2}(:\d{2}(\.\d{1,9})?)?)?)?)?)/,
          ) ||
          stream.match(/^\d+D(\d{2}(:\d{2}(:\d{2}(\.\d{1,9})?)?)?)/) ||
          stream.match(/^\d{2}:\d{2}(:\d{2}(\.\d{1,9})?)?/) ||
          stream.match(/^\d+[ptuv]{1}/)
        )
          t = 'temporal';
        else if (
          stream.match(/^0[NwW]{1}/) ||
          stream.match(/^0x[\da-fA-F]*/) ||
          stream.match(/^[01]+[b]{1}/) ||
          stream.match(/^\d+[chijn]{1}/) ||
          stream.match(/-?\d*(\.\d*)?(e[+\-]?\d+)?(e|f)?/)
        )
          t = 'number';
        return t && (!(c = stream.peek()) || E.test(c))
          ? t
          : (stream.next(), 'error');
      }
      if (/[A-Za-z]|\./.test(c))
        return (
          stream.eatWhile(/[A-Za-z._\d]/),
          keywords.test(stream.current()) ? 'keyword' : 'variable'
        );
      if (/[|/&^!+:\\\-*%$=~#;@><\.,?_\']/.test(c)) return null;
      if (/[{}\(\[\]\)]/.test(c)) return null;
      return 'error';
    }
    function tokenLineComment(stream, state) {
      return (
        stream.skipToEnd(),
        /\/\s*$/.test(stream.current())
          ? (state.tokenize = tokenBlockComment)(stream, state)
          : (state.tokenize = tokenBase),
        'comment'
      );
    }
    function tokenBlockComment(stream, state) {
      var f = stream.sol() && stream.peek() == '\\';
      stream.skipToEnd();
      if (f && /^\\\s*$/.test(stream.current())) state.tokenize = tokenBase;
      return 'comment';
    }
    function tokenCommentToEOF(stream) {
      return stream.skipToEnd(), 'comment';
    }
    function tokenString(stream, state) {
      var escaped = false,
        next,
        end = false;
      while ((next = stream.next())) {
        if (next == '"' && !escaped) {
          end = true;
          break;
        }
        escaped = !escaped && next == '\\';
      }
      if (end) state.tokenize = tokenBase;
      return 'string';
    }
    function pushContext(state, type, col) {
      state.context = {
        prev: state.context,
        indent: state.indent,
        col: col,
        type: type,
      };
    }
    function popContext(state) {
      state.indent = state.context.indent;
      state.context = state.context.prev;
    }
    return {
      startState: function() {
        return { tokenize: tokenBase, context: null, indent: 0, col: 0 };
      },
      token: function(stream, state) {
        if (stream.sol()) {
          if (state.context && state.context.align == null)
            state.context.align = false;
          state.indent = stream.indentation();
        }
        //if (stream.eatSpace()) return null;
        var style = state.tokenize(stream, state);
        if (
          style != 'comment' &&
          state.context &&
          state.context.align == null &&
          state.context.type != 'pattern'
        ) {
          state.context.align = true;
        }
        if (curPunc == '(') pushContext(state, ')', stream.column());
        else if (curPunc == '[') pushContext(state, ']', stream.column());
        else if (curPunc == '{') pushContext(state, '}', stream.column());
        else if (/[\]\}\)]/.test(curPunc)) {
          while (state.context && state.context.type == 'pattern')
            popContext(state);
          if (state.context && curPunc == state.context.type) popContext(state);
        } else if (
          curPunc == '.' &&
          state.context &&
          state.context.type == 'pattern'
        )
          popContext(state);
        else if (/atom|string|variable/.test(style) && state.context) {
          if (/[\}\]]/.test(state.context.type))
            pushContext(state, 'pattern', stream.column());
          else if (state.context.type == 'pattern' && !state.context.align) {
            state.context.align = true;
            state.context.col = stream.column();
          }
        }
        return style;
      },
      indent: function(state, textAfter) {
        var firstChar = textAfter && textAfter.charAt(0);
        var context = state.context;
        if (/[\]\}]/.test(firstChar))
          while (context && context.type == 'pattern') context = context.prev;
        var closing = context && firstChar == context.type;
        if (!context) return 0;
        else if (context.type == 'pattern') return context.col;
        else if (context.align) return context.col + (closing ? 0 : 1);
        else return context.indent + (closing ? 0 : indentUnit);
      },
    };
  });
  CodeMirror.defineMIME('text/x-q', 'q');
});
